package net.sf.fakenames.fddemo;

import android.app.Instrumentation;
import android.content.Context;
import android.content.res.AssetManager;
import android.os.Handler;
import android.os.Looper;
import android.os.storage.OnObbStateChangeListener;
import android.os.storage.StorageManager;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;

import net.sf.xfd.DirFd;
import net.sf.xfd.Fd;
import net.sf.xfd.MountInfo;
import net.sf.xfd.NativeBits;
import net.sf.xfd.OS;
import net.sf.xfd.SelectorThread;

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.nio.channels.Selector;
import java.nio.channels.spi.AbstractSelector;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import static com.google.common.truth.Truth.assertThat;

@RunWith(AndroidJUnit4.class)
public class OpenTests {
    private static final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();

    private static final OS os;

    private static File pipe;

    static {
        try {
            os = OS.getInstance();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @BeforeClass
    public static void makePipe() throws IOException {
        final Context ctx = instrumentation.getTargetContext();

        pipe = ctx.getFileStreamPath("pipe");

        //noinspection ResultOfMethodCallIgnored
        pipe.delete();

        os.mknodat(DirFd.NIL, pipe.getAbsolutePath(), OS.S_IFIFO | OS.DEF_FILE_MODE, 0);
    }

    @Test(expected = InterruptedIOException.class)
    public void openPipe() throws IOException {
        Thread.currentThread().interrupt();

        @Fd int pipeFd = os.open(pipe.getAbsolutePath(), OS.O_RDONLY, 0);

        // should not happen
        os.close(pipeFd);
    }

    @Test(expected = InterruptedIOException.class)
    public void openPipeThenInterrupt() throws IOException {
        final Thread t = Thread.currentThread();

        new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
            @Override
            public void run() {
                t.interrupt();
            }
        }, 2000);

        @Fd int pipeFd = os.open(pipe.getAbsolutePath(), OS.O_RDONLY, 0);

        // should not happen
        os.close(pipeFd);
    }

    @Test
    public void openPipeSync() throws IOException {
        new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
            @Override
            public void run() {
                try {
                    @Fd int pipeFd = os.open(pipe.getAbsolutePath(), OS.O_WRONLY, 0);

                    os.close(pipeFd);
                } catch (IOException e) {
                    throw new AssertionError(e);
                }
            }
        }, 2000);

        @Fd int pipeFd = os.open(pipe.getAbsolutePath(), OS.O_RDONLY, 0);

        os.close(pipeFd);
    }

    @Test
    public void openMultiple() throws IOException {
        Thread.currentThread().interrupt();

        boolean caught = false;

        try {
            //noinspection CheckResult
            os.open(pipe.getAbsolutePath(), OS.O_RDONLY, 0);
        } catch (InterruptedIOException iie) {
            caught = true;
        }

        if (!caught) throw new AssertionError();

        Thread.interrupted();

        new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
            @Override
            public void run() {
                try {
                    @Fd int pipeFd = os.open(pipe.getAbsolutePath(), OS.O_WRONLY, 0);

                    os.close(pipeFd);
                } catch (IOException e) {
                    throw new AssertionError(e);
                }
            }
        }, 2000);

        int pipeFd = os.open(pipe.getAbsolutePath(), OS.O_RDONLY, 0);

        os.close(pipeFd);
    }

    @Test
    public void openPipeNonblocking() throws IOException {
        //noinspection WrongConstant,PointlessBitwiseExpression
        @Fd int pipeFd = os.open(pipe.getAbsolutePath(), OS.O_RDONLY | NativeBits.O_NONBLOCK, 0);

        os.close(pipeFd);
    }

    @AfterClass
    public static void deletePipe() throws IOException {
        os.unlinkat(DirFd.NIL, pipe.getAbsolutePath(), 0);
    }
}
